<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="./node_modules/d3/dist/d3.min.js"></script>
</head>
<body>

</body>
</html>
<script>
    // Width and height
    var width = 800;
    var height = 500;
    var circleRadius = 10;
    // Original data
    let nodes = [
            {name: "湖南邵阳"},
            {name: "山东莱州"},
            {name: "广东阳江"},
            {name: "山东枣庄"},
            {name: "泽"},
            {name: "恒"},
            {name: "鑫"},
            {name: "明山"},
            {name: "班长"}
        ],
        edges = [
            {source: 0, target: 4, relation: "籍贯", value: 1.3},
            {source: 4, target: 5, relation: "舍友", value: 1},
            {source: 4, target: 6, relation: "舍友", value: 1},
            {source: 4, target: 7, relation: "舍友", value: 1},
            {source: 1, target: 6, relation: "籍贯", value: 2},
            {source: 2, target: 5, relation: "籍贯", value: 0.9},
            {source: 3, target: 7, relation: "籍贯", value: 1},
            {source: 5, target: 6, relation: "同学", value: 1.6},
            {source: 6, target: 7, relation: "朋友", value: 0.7},
            {source: 6, target: 8, relation: "职责", value: 2}
        ];

    /* *
     * 创建svg
     * */
    const padding = {top: 10, right: 10, bottom: 10, left: 10};
    const body = d3.select('body');
    body.append('svg')
        .attr('width', width)
        .attr('height', height);
    const svg = body.select('svg');
    // 创建装forceSimulation的盒子
    const g = svg.append('g')
        .attr('transform', `translate(${padding.left},${padding.top})`);


    /* *
     * 创建颜色比例尺
     * */
    const colorScale = d3.scaleOrdinal()
        .domain(d3.range(nodes.length))
        .range(d3.schemeCategory10);

    /* *
     * 新建一个力导向图
     * */
    const forceSimulation = d3.forceSimulation()
        .force('link', d3.forceLink())
        .force('change', d3.forceManyBody())
        .force('center', d3.forceCenter());

    /* *
     * 生成节点数据
     * */
    forceSimulation.nodes(nodes)
    // 力导向图是不断运动的，每一时刻都在发生更新，tick需要不断更新节点和连接的位置(这个方法很重要)
        .on('tick', ticked);

    /* *
     * 生成边界数据
     * */
    forceSimulation.force('link')
        .links(edges)
        .distance((d) => {
            return d.value * 100;
        });

    /* *
     * 设置图形的中心位置
     * */
    forceSimulation.force('center')
        .x(width / 2)
        .y(height / 2);

    /* *
     * 绘制边
     * */
    let links = g.append('g')
        .selectAll('line')
        .data(edges)
        .enter()
        .append('line')
        .attr('stroke', function (d, i) {
            return colorScale(i)
        })
        .attr('stroke-width', 1);

    /* *
    * 边上的文字
    * */
    let linksText = g.append('g')
        .selectAll('text')
        .data(edges)
        .enter()
        .append('text')
        .text(d => {
            return d.relation;
        });

    /* *
    * 创建存放节点和对应文字的分组
    * */
    var gs = g.selectAll('.circleText')
        .data(nodes)
        .enter()
        .append('g')
        .attr('transform', d => {
            const cx = d.x;
            const cy = d.y;
            return `translate(${cx},${cy})`
        }).call(
            d3.drag()
                .on('start', started)
                .on('drag', dragged)
                .on('end', ended)
        );

    /* *
    * 添加节点和文字
    * */
    gs.append('circle')
        .attr('r', circleRadius)
        .attr('fill', (d, i) => {
            return colorScale(i)
        });
    gs.append('text')
        .attr('x', -10)
        .attr('y', -20)
        .attr('dy', 10)
        .text(d => {
            return d.name;
        });

    function ticked() {
        links
            .attr('x1', d => {
                return d.source.x;
            })
            .attr('y1', d => {
                return d.source.y;
            })
            .attr('x2', d => {
                return d.target.x;
            })
            .attr('y2', d => {
                return d.target.y
            });

        linksText
            .attr('x', d => {
                return (d.target.x + d.source.x) / 2;
            })
            .attr('y', d => {
                return (d.target.y + d.source.y) / 2;
            });



        gs.attr('transform', d => {
            return `translate(${d.x},${d.y})`;
        });
    }

    // 拖拽开始 拖拽 拖拽结束
    function started(d) {
        if (!d3.event.active) {
            // 设置衰减系数，对节点位置移动过程的模拟，数值越高移动越快，数值范围[0,1]
            forceSimulation.alphaTarget(0.8).restart();
        }
        d.fx = d.x;
        d.fy = d.y;
    }

    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }

    function ended(d) {
        if (!d3.event.active) {
            forceSimulation.alphaTarget(0);
        }
        d.fx = null;
        d.fy = null;
    }

</script>
